package com.ylr.base.common.utils;

import com.ylr.base.common.enums.ResultEnum;
import com.ylr.base.common.exception.YlrException;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Objects;

/**
 * Created by IntelliJ IDEA.
 *
 * @author wyw
 * date: 2023-09-04 10:18:40
 * className: MD5Utils MD5加密工具类
 * version: 1.0
 * description:
 */
public class MD5Utils {

    private static final Logger log = LoggerFactory.getLogger(MD5Utils.class);

    /**
     * MD5加密
     * @param plainText 明文
     * @return 密文
     */
    public static String encodeMD5(String plainText) {
        if (StringUtils.isBlank(plainText)) {
            log.error("MD5加密参数为空");
            throw new YlrException(ResultEnum.SERVICE_FAIL);
        }
        return doEncodeMD5(plainText, "", false);
    }

    /**
     * MD5加密
     * @param plainText 明文
     * @param secretKey 密钥
     * @return 密文
     */
    public static String encodeMD5(String plainText, String secretKey) {
        if (StringUtils.isBlank(plainText) || Objects.isNull(secretKey)) {
            log.error("MD5加密参数为空");
            throw new YlrException(ResultEnum.SERVICE_FAIL);
        }
        return doEncodeMD5(plainText, secretKey, false);
    }

    /**
     * MD5加密
     * 使用明文的前二十个字符当作密钥
     * @param plainText 明文
     * @return 密文
     */
    public static String encodeMD5BySelfSecret(String plainText) {
        if (StringUtils.isBlank(plainText)) {
            log.error("MD5加密参数为空");
            throw new YlrException(ResultEnum.SERVICE_FAIL);
        }
        String secretKey = plainText.substring(0, Math.min(20, plainText.length()));
        return doEncodeMD5(plainText, secretKey, false);
    }

    /**
     * MD5加密
     * @param plainText 明文
     * @param toUpper   密文转大写
     * @return 密文
     */
    public static String encodeMD5(String plainText, boolean toUpper) {
        if (StringUtils.isBlank(plainText)) {
            log.error("MD5加密参数为空");
            throw new YlrException(ResultEnum.SERVICE_FAIL);
        }
        return doEncodeMD5(plainText, "", toUpper);
    }

    /**
     * MD5加密
     * @param plainText 明文
     * @param secretKey 密钥
     * @param toUpper   密文转大写
     * @return 密文
     */
    public static String encodeMD5(String plainText, String secretKey, boolean toUpper) {
        if (StringUtils.isBlank(plainText) || Objects.isNull(secretKey)) {
            log.error("MD5加密参数为空");
            throw new YlrException(ResultEnum.SERVICE_FAIL);
        }
        return doEncodeMD5(plainText, secretKey, toUpper);
    }

    /**
     * MD5加密
     * 使用明文的前二十个字符当作密钥
     * @param plainText 明文
     * @param toUpper   密文转大写
     * @return 密文
     */
    public static String encodeMD5BySelfSecret(String plainText, boolean toUpper) {
        if (StringUtils.isBlank(plainText)) {
            log.error("MD5加密参数为空");
            throw new YlrException(ResultEnum.SERVICE_FAIL);
        }
        String secretKey = plainText.substring(0, Math.min(20, plainText.length()));
        return doEncodeMD5(plainText, secretKey, toUpper);
    }

    /**
     * 执行MD5加密
     * 将明文插入密钥中间，在进行MD5加密
     * @param plainText 明文
     * @param secretKey 密钥
     * @return 密文
     */
    private static String doEncodeMD5(String plainText, String secretKey, boolean toUpper) {
        byte[] plainTextBytes = plainText.getBytes(StandardCharsets.UTF_8);
        byte[] secretKeyBytes = secretKey.getBytes(StandardCharsets.UTF_8);
        byte[] bytes = new byte[plainTextBytes.length + secretKeyBytes.length];
        int middleIndex = secretKeyBytes.length / 2;
        int index = 0;
        if (middleIndex > 0) {
            // 数组复制
            System.arraycopy(secretKeyBytes, 0, bytes, index, middleIndex);
            // 下标变更
            index += middleIndex;
        }
        System.arraycopy(plainTextBytes, 0, bytes, index, plainTextBytes.length);
        if (secretKeyBytes.length > middleIndex) {
            index += plainTextBytes.length;
            System.arraycopy(secretKeyBytes, middleIndex, bytes, index, secretKeyBytes.length - middleIndex);
        }
        try {
            // 获取加密对象
            MessageDigest messageDigest = MessageDigest.getInstance("MD5");
            // 加密操作
            return ByteUtils.bytesToHex(messageDigest.digest(bytes), toUpper);
        } catch (Exception e) {
            log.error("MD5加密失败。plainText={} secretKey={} message={}", plainText, secretKey, e.getMessage(), e);
            throw new YlrException(ResultEnum.SERVICE_FAIL);
        }
    }

    /**
     * 文件MD5加密
     * @param file 文件对象
     * @return 密文
     */
    public static String fileMD5(File file) {
        if (Objects.isNull(file)) {
            log.error("文件参数为空");
            throw new YlrException(ResultEnum.SERVICE_FAIL);
        }
        // 获取加密对象
        MessageDigest messageDigest;
        try {
            messageDigest = MessageDigest.getInstance("MD5");
        } catch (NoSuchAlgorithmException e) {
            log.error("获取MD5加密对象失败。 message={}", e.getMessage(), e);
            throw new YlrException(ResultEnum.SERVICE_FAIL);
        }
        FileInputStream fileInputStream;
        try {
            // 获取文件输入流对象
            fileInputStream = new FileInputStream(file);
        } catch (FileNotFoundException e) {
            log.error("获取文件输入流对象失败。 message={}", e.getMessage(), e);
            throw new YlrException(ResultEnum.SERVICE_FAIL);
        }
        // 文件内容读取缓存数组
        byte[] cacheArray = new byte[1024];
        // 单次读取到文件内容字节总数
        int readCount;

        try {
            while ((readCount = fileInputStream.read(cacheArray)) != -1) {
                // 对文件内容缓存数组放入MD5加密摘要中
                messageDigest.update(cacheArray, 0, readCount);
            }
        } catch (IOException e) {
            log.error("文件流数据读取异常。message={}", e.getMessage(), e);
            throw new YlrException(ResultEnum.SERVICE_FAIL);
        } catch (Exception e) {
            log.error("文件MD5加密异常。message={}", e.getMessage(), e);
            throw new YlrException(ResultEnum.SERVICE_FAIL);
        } finally {
            try {
                // 关闭文件输入流对象
                fileInputStream.close();
            } catch (IOException e) {
                log.error("文件输入流关闭异常。message={}", e.getMessage(), e);
            }
        }
        // MD5加密
        byte[] result = messageDigest.digest();
        // 结果封装
        return ByteUtils.bytesToHex(result);
    }

}
